const t = require('through2');
const fs = require('fs');
const gutil = require('gulp-util');
const Path = require('path');
const DefaultLoader = require('./loaders/DefaultLoader');
const VFile = require('vinyl');

let gopt = {
    loaders: {
        '.js': 'JsLoader',
        '.jsx': 'JsLoader',
        '.ts': 'TsLoader',
        '.tsx': 'TsLoader',
        '.json': 'JsonLoader',
        '.less': 'LessLoader'
    },
    ext: ['.js', '.jsx', '.json', '.ts', '.tsx', '.less'],
    host: ''
};

function obtainLoader(loader) {
    if (typeof loader === 'string') {
        return require('./loaders/' + loader);
    } else {
        return loader;
    }
}


async function loadModule(gulp, module, loaders, opt, cache) {
    let file = module.file;
    let oldAbsPath = fs.realpathSync(module.file.path);
    if (cache[oldAbsPath]) {
        return cache[oldAbsPath];
    }
    console.log('load module', oldAbsPath);
    let ext = Path.extname(file.path);
    //let oldPath = file.relative;
    let loader = loaders[ext] || DefaultLoader;

    let newModule = loader(module, opt);
    if (newModule instanceof Promise) {
        newModule = await newModule;
    }
    cache[oldAbsPath] = newModule;
    if (opt.recursive) {
        let dependencyModule = [];
        for (let loc of Object.values(newModule.dependency)) {
            if (loc.type === 'normal') {
                let newFile = new VFile({
                    path: loc.src,
                    cwd: file.cwd,
                    base: file.base,
                    contents: fs.readFileSync(loc.src)
                });
                let subModule = {
                    file: newFile,
                    dependency: {}
                };
                dependencyModule.push(await loadModule(gulp, subModule, loaders, opt, cache));
            }
        }
        newModule.dependencyModule = dependencyModule;
    }
    //console.log(oldPath, '->', newModule.file.relative);
    return newModule;
}

function ZPack(aopt) {
    let opt = Object.assign({}, gopt, aopt);
    let loaders = {};
    for (let i in opt.loaders) {
        loaders[i] = obtainLoader(opt.loaders[i]);
    }

    return t.obj(function pack(file, enc, cb) {
        if (file.isNull()) {
            cb(null, file);
            return;
        }
        if (file.isStream()) {
            cb(new gutil.PluginError('gulp-zpack', 'Streaming not supported'));
            return;
        }
        let module = {
            file,
            dependency: {}
        };
        let cache = {};
        loadModule(this, module, loaders, opt, cache).then(() => {
            for (let e of Object.values(cache)) {
                this.push(e.file);
            }
            cb();
        }).catch(err => {
            console.error(err);
            if (opt.avoidError) {
                cb();
            } else {
                cb(new gutil.PluginError(err));
            }
        });
    });
}

module.exports = ZPack;
